//
//  vector_base.hpp
//  utilities
//
//  Created by Sam Jaffe on 9/8/14.
//  Copyright (c) 2014 Sam Jaffe. All rights reserved.
//

#pragma once

#include <stdexcept>

namespace math { namespace vector {

    template <size_t D, typename T>
    vector<D, T>::vector(std::initializer_list<T> vals) :
    detail::_vector<D, T>(vals) {
        
    }
    
    template <size_t D, typename T>
    vector<D, T>::vector(std::valarray<T>&& other) :
    detail::_vector<D, T>(std::forward<std::valarray<T>>(other)) {
        
    }
    
    template <typename T>
    vector<3, T>::vector(std::initializer_list<T> vals) :
    detail::_vector<3, T>(vals) {
        
    }
    
    template <typename T>
    vector<3, T>::vector(std::valarray<T>&& other) :
    detail::_vector<3, T>(std::forward<std::valarray<T>>(other)) {
        
    }
    
    template <typename T>
    vector<3, T>::vector(const T& x, const T& y, const T& z) :
    detail::_vector<3, T>{x, y, z} {
        
    }
    
    template <typename T>
    vector<3, T> vector<3, T>::cross(const vector<3, T>& v) const {
        return vector<3, T>(this->at(1)*v[2] - this->at(2)*v[1],
                            this->at(2)*v[0] - this->at(0)*v[2],
                            this->at(0)*v[1] - this->at(1)*v[0]);
    }
    
    template <typename T>
    vector<2, T>::vector(std::initializer_list<T> vals) :
    detail::_vector<2, T>(vals) {
        
    }
    
    template <typename T>
    vector<2, T>::vector(std::valarray<T>&& other) :
    detail::_vector<2, T>(std::forward<std::valarray<T>>(other)) {
        
    }
    
    template <typename T>
    vector<2, T>::vector(const T& x, const T& y) :
    detail::_vector<2, T>{x, y} {
        
    }
    
    template <typename T>
    vector<3, T> vector<2, T>::cross(const vector<2, T>& v) const {
        return vector<3, T>(0, 0, (this->at(0)*v[1]) - (v[0]*this->at(1)));
    }

    template <size_t D, typename T>
    detail::_vector<D, T>::_vector(std::initializer_list<T> vals) :
    impl(vals) {
        impl.resize(D);
    }
    
    template <size_t D, typename T>
    detail::_vector<D, T>::_vector(std::valarray<T>&& other) :
    impl(std::forward<std::valarray<T>>(other)) {
        impl.resize(D);
    }
    
    template <size_t D, typename T>
    T& detail::_vector<D, T>::operator[] (size_t index) {
        dirty_ = true;
        return impl[index];
    }
    
    template <size_t D, typename T>
    constexpr inline const T& detail::_vector<D, T>::operator[] (size_t index) const noexcept {
        return impl[index];
    }
    
    template <size_t D, typename T>
    T& detail::_vector<D, T>::at(size_t index) {
        if (index < 0 || index > D) {
            throw std::out_of_range("");
        }
        return impl[index];
    }
    
    template <size_t D, typename T>
    const T& detail::_vector<D, T>::at(size_t index) const {
        if (index < 0 || index > D) {
            throw std::out_of_range("");
        }
        return impl[index];
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator+=(const vector<D, T>& other) {
        dirty_ = true;
        impl += other.impl;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator-=(const vector<D, T>& other) {
        dirty_ = true;
        impl -= other.impl;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator*=(const vector<D, T>& other) {
        dirty_ = true;
        impl *= other.impl;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator/=(const vector<D, T>& other) {
        dirty_ = true;
        impl /= other.impl;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator+=(const T& c) {
        dirty_ = true;
        impl += c;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator-=(const T& c) {
        dirty_ = true;
        impl -= c;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator*=(const T& c) {
        dirty_ = true;
        impl *= c;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T>& detail::_vector<D, T>::operator/=(const T& c) {
        dirty_ = true;
        impl /= c;
        return static_cast<vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator+(const vector<D, T>& other) const {
        return vector<D, T>(impl + other.impl);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator-(const vector<D, T>& other) const {
        return vector<D, T>(impl - other.impl);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator*(const vector<D, T>& other) const {
        return vector<D, T>(impl * other.impl);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator/(const vector<D, T>& other) const {
        return vector<D, T>(impl / other.impl);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator+(const T& c) const {
        return vector<D, T>(impl + c);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator-(const T& c) const {
        return vector<D, T>(impl - c);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator*(const T& c) const {
        return vector<D, T>(impl * c);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator/(const T& c) const {
        return vector<D, T>(impl / c);
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::operator-() const {
        return vector<D, T>(-impl);
    }
    
    template <size_t D, typename T>
    template <size_t D2, typename>
    vector<D2, T> detail::_vector<D, T>::resize() const {
        return vector<D2, T>{std::valarray<T>{impl}};
    }
    
    template <size_t D, typename T>
    T detail::_vector<D, T>::dot(const detail::_vector<D, T>& v) const {
        return (impl*v.impl).sum();
    }
    
    template <size_t D, typename T>
    T detail::_vector<D, T>::lengthSquared() const {
        refresh_lengths();
        return lsquared_;
    }
    
    template <size_t D, typename T>
    void detail::_vector<D, T>::refresh_lengths() const {
        if (dirty_) {
            lsquared_ = this->dot(*this);
            length_ = sqrt(lsquared_);
            dirty_ = false;
        }
    }
    
    template <size_t D, typename T>
    T detail::_vector<D, T>::length() const {
        refresh_lengths();
        return length_;
    }
    
    template <size_t D, typename T>
    vector<D, T> detail::_vector<D, T>::unit() const {
        return *this/length();
    }
} }
